package com.piece.core.redis.util;

import com.piece.core.framework.util.basic.SpringUtil;
import com.piece.core.framework.util.string.JsonUtil;
import com.piece.core.framework.util.string.StringUtil;
import com.piece.core.redis.dto.RedisMonitorDTO;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Redis服务端信息
 */
public class RedisInfo {

    private RedisTemplate redisTemplate;

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    private static RedisInfo instance;
    private static ReentrantLock lock = new ReentrantLock();

    public static RedisInfo getInstance() {
        if (null == instance) {
            lock.lock();
            if (null == instance) {
                instance = new RedisInfo();
                RedisTemplate redisTemplate = (RedisTemplate) SpringUtil.getBean("redisTemplate");
                instance.setRedisTemplate(redisTemplate);
            }
            lock.unlock();
        }
        return instance;
    }

    public RedisMonitorDTO getRedisInfo() {
        Properties info = (Properties) this.redisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);
        Long dbSize = (Long) this.redisTemplate.execute(RedisServerCommands::dbSize);
        Properties commandStats = (Properties) this.redisTemplate.execute((
                RedisCallback<Properties>) connection -> connection.info("commandstats"));

        assert null != commandStats;

        RedisMonitorDTO result =RedisMonitorDTO.builder()
                .info(info)
                .dbSize(dbSize)
                .commandStats(new ArrayList<>(commandStats.size()))
                .build();
        commandStats.forEach((key, value) -> {
            result.getCommandStats().add(RedisMonitorDTO.CommandStat.builder()
                    .command(StringUtil.subAfter((String) key, "cmdstat_", false))
                    .calls(Integer.valueOf(StringUtil.subBetween((String) value, "calls=", ",")))
                    .usec(Long.valueOf(StringUtil.subBetween((String) value, "usec=", ",")))
                    .build());
        });

        return result;
    }

    public Set<String> getRedisKeyList(String match) {
        Set<String> keys = new LinkedHashSet<>();
        this.redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            ScanOptions scanOptions;
            if (StringUtil.isNotEmpty(match)) {
                scanOptions = ScanOptions.scanOptions().match("*" + match + "*").count(100).build();
            } else {
                scanOptions = ScanOptions.scanOptions().count(100).build();
            }
            try (Cursor<byte[]> cursor = connection.scan(scanOptions)) {
                cursor.forEachRemaining(value -> keys.add(StringUtil.getStringCharset(value, StandardCharsets.UTF_8.name())));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return keys;
        });
        return keys;
    }

    public String getRedisKeyValue(String key) {
        boolean success = false;
        Object value = null;
        try {
            value = redisTemplate.opsForValue().get(key);
            success = true;
        } catch (Exception e) {}

        if (!success) {
            try {
                value = redisTemplate.opsForHash().keys(key);
                success = true;
            } catch (Exception e) {}
        }

        if (!success) {
            try {
                value = redisTemplate.opsForList().range(key, 0, -1);
                success = true;
            } catch (Exception e) {}
        }

        if (success) {
            return JsonUtil.toJson(value);
        } else {
            return "非基本类型，无法解析数值";
        }
    }

    public boolean delete(String key) {
        return this.redisTemplate.delete(key);
    }

    public boolean deleteTemplate(String template) {
        Set<String> keys = new LinkedHashSet<>();
        this.redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(template).build())) {
                cursor.forEachRemaining(value -> keys.add(StringUtil.getStringCharset(value, StandardCharsets.UTF_8.name())));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return keys;
        });
        if (CollectionUtils.isNotEmpty(keys)) {
            this.redisTemplate.delete(keys);
        }
        return true;
    }
}
